Skip to content

fix(agents): normalize Command-wrapped tool results - #4977

Open
BetterAndBetterII wants to merge 4 commits into
bytedance:mainfrom
BetterAndBetterII:fix/command-tool-result-semantics
Open

fix(agents): normalize Command-wrapped tool results#4977
BetterAndBetterII wants to merge 4 commits into
bytedance:mainfrom
BetterAndBetterII:fix/command-tool-result-semantics

Conversation

@BetterAndBetterII

Copy link
Copy Markdown

Closes #4976

Why

ToolMessages returned inside Command(update={"messages": [...]}) skip result normalization and progress tracking. Error payloads then get a default success receipt even though the content is Error: ....

What changed

  • normalize_tool_result now stamps matching ToolMessages inside a Command (same metadata contract as a bare result; producer-supplied meta is left alone).
  • ToolErrorHandlingMiddleware passes the current tool_call_id into that normalization.
  • ToolProgressMiddleware assesses the matching Command message instead of ignoring every Command.

Receipts already match Command messages by tool_call_id; they now see the normalized status.

Surface area

  • Agents / LangGraph — agent node, graph wiring, langgraph.json, or prompt change

Bug fix verification

  • Test path: backend/tests/test_command_tool_result_semantics.py
  • Red on main, green on this branch: yes
  • 20 cases: bare-result controls, Command success/error/partial_success, producer-meta preservation, unrelated messages left intact, sync/async error-handling and progress paths, receipts, and setup_agent / view_image through the production middleware chain.

Validation

cd backend && .venv/bin/python -m pytest tests/test_command_tool_result_semantics.py tests/test_tool_error_handling_middleware.py tests/test_tool_progress_middleware.py tests/test_tool_receipt.py tests/test_tool_receipt_middleware.py tests/test_tool_result_meta.py -q

20 new tests passed. Related suites passed except one pre-existing test_build_subagent_runtime_middlewares_threads_app_config_to_llm_middleware ordering failure on main.

AI assistance

Tool(s) used: none

How you used it: n/a

  • I've read and understand every line of this change and take responsibility for it — it's not unreviewed AI output.

Command-wrapped ToolMessages skipped result metadata and progress
tracking, so error receipts could be recorded as success.
@CLAassistant

CLAassistant commented Aug 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added area:agents Agents, subagents, graph wiring, prompts, langgraph.json needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/L PR changes 300-700 lines labels Aug 23, 2026
continue
if tool_call_id and str(message.tool_call_id) != tool_call_id:
continue
normalize_tool_message(message)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Delegated task failures still get a success receipt through this path. _task_result_command carries the authoritative subagent_status (failed, cancelled, timed_out, or polling_timed_out) but leaves ToolMessage.status at LangChain's default success; its text starts with Task failed... / Task cancelled..., not the Error: prefix that normalize_tool_message recognizes. As a result this call stamps deerflow_tool_meta.status="success", and the outer receipt layer records success as well. I reproduced all four non-completed statuses with the production error-handling + receipt chain. Please derive task metadata from the structured subagent_status (or stamp it in _task_result_command) and add regression cases for these statuses, otherwise one of the principal Command producers named in #4976 remains unfixed.

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Command handling is correct for the newly covered setup_agent/view_image cases, but delegated task failures remain misclassified: failed, cancelled, timed-out, and polling-timeout task results all receive deerflow_tool_meta.status=success and therefore a success receipt. I reproduced this against the current head using the production error-handling and receipt chain. Please use the structured subagent_status when normalizing task results and add coverage for the non-completed statuses. Details are in the inline comment.

Delegated task Commands leave ToolMessage.status at success and do not
use an Error: content prefix, so normalize_tool_message was labeling
failed/cancelled/timed_out results as success. Honor structured
subagent_status before content heuristics and cover the four statuses.
@BetterAndBetterII

Copy link
Copy Markdown
Author

Addressed the delegated-task case: normalize_tool_message now honors subagent_status in {failed, cancelled, timed_out, polling_timed_out} and stamps error meta instead of success. Added regression coverage in tests/test_tool_result_meta.py.

@WillemJiang

Copy link
Copy Markdown
Collaborator

@BetterAndBetterII, please fix the lint error by running 'make format ' in the backend directory.

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previously reported delegated-task status issue is fixed at this head, and the PR-specific regression tests pass. One CI blocker remains: the newly edited result-meta test file is not Ruff-formatted. Details are in the inline comment.

assert m["error_type"] == expected_error_type, f"{content!r} → expected {expected_error_type!r}, got {m['error_type']!r}"

# ---------------------------------------------------------------------------
# Structured subagent_status failures (delegated task Command results)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Format this block so backend lint can pass

The required lint-backend check currently fails because uv run ruff format --check . reports that this file would be reformatted. Ruff only requires an additional blank line before this section header, but the formatting diff needs to be committed before the PR can pass CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agents Agents, subagents, graph wiring, prompts, langgraph.json needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/L PR changes 300-700 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Command-wrapped ToolMessages bypass result normalization and report error receipts as success

4 participants